index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. "use client";
  2. import { loginApi, registerApi, userInfoApi } from "@/api/login";
  3. import Box from "@/components/Box";
  4. import ButtonOwn from "@/components/ButtonOwn";
  5. import MobileField from "@/components/Fields/MobileField";
  6. import { useEventPoint } from "@/hooks/useEventPoint";
  7. import { Link, useRouter } from "@/i18n/routing";
  8. import { useSystemStore } from "@/stores/useSystemStore";
  9. import { useUserInfoStore } from "@/stores/useUserInfoStore";
  10. import { emailReg, neReg } from "@/utils";
  11. import { setCookies } from "@/utils/Cookies";
  12. import { Local } from "@/utils/storage";
  13. import {
  14. Checkbox,
  15. DatePicker,
  16. DatePickerRef,
  17. Form,
  18. Input,
  19. Radio,
  20. Space,
  21. TextArea,
  22. Toast,
  23. } from "antd-mobile";
  24. import { PickerDate } from "antd-mobile/es/components/date-picker/util";
  25. import clsx from "clsx";
  26. import dayjs from "dayjs";
  27. import { useTranslations } from "next-intl";
  28. import { useSearchParams } from "next/navigation";
  29. import { FC, RefObject, useEffect, useRef, useState } from "react";
  30. import styles from "./style.module.scss";
  31. interface MobileFieldProps {
  32. value?: string;
  33. onChange?: (value: string) => void;
  34. }
  35. // const MobileField: FC<MobileFieldProps> = (props) => {
  36. // const t = useTranslations("form");
  37. // const { value, onChange } = props;
  38. //
  39. // const changeHandler = (value: string) => {
  40. // let newAmount = value.replace(/[^0-9]/g, "");
  41. // if (onChange) {
  42. // onChange(newAmount);
  43. // }
  44. // };
  45. // return (
  46. // <>
  47. // <Space align="center" className={"text-[#ccc]"}>
  48. // <Space align="center">+55</Space>
  49. // <Input
  50. // placeholder={t("phone")}
  51. // maxLength={11}
  52. // value={value}
  53. // onChange={(e) => changeHandler(e)}
  54. // />
  55. // </Space>
  56. // </>
  57. // );
  58. // };
  59. const SexMobile: FC<MobileFieldProps> = (props) => {
  60. const { value, onChange } = props;
  61. const handler = () => {
  62. console.log(`🚀🚀🚀🚀🚀-> in index.tsx on 58`, 123);
  63. };
  64. return (
  65. <div className={"w-[100%] border-4"} onClick={handler}>
  66. <Space align="center" className={"border-4 text-[#ccc]"}>
  67. <div className={"h-full w-[100%]"}>{value}</div>
  68. </Space>
  69. </div>
  70. );
  71. };
  72. interface FormProps {
  73. /**
  74. * 通讯地址
  75. */
  76. address?: string;
  77. /**
  78. * 用户头像
  79. */
  80. avatar_url?: string;
  81. /**
  82. * 生日:yyyy-MM-dd
  83. */
  84. birthday?: string;
  85. /**
  86. * 邮箱地址
  87. */
  88. email?: string;
  89. /**
  90. * 真实姓名
  91. */
  92. nick_name?: string;
  93. /**
  94. * 外部ID
  95. */
  96. open_id?: string;
  97. /**
  98. * 身份护照
  99. */
  100. passport?: string;
  101. /**
  102. * 用户密码
  103. */
  104. pwd: string;
  105. /**
  106. * 推荐码
  107. */
  108. referrer_code?: string;
  109. /**
  110. * 用户名
  111. */
  112. user_name?: string;
  113. /**
  114. * 用户电话号码
  115. */
  116. user_phone: string;
  117. /**
  118. * 用户类型
  119. */
  120. user_type?: string;
  121. sex?: number;
  122. /**
  123. * 渠道链接
  124. */
  125. channel_code?: string;
  126. code_phone?: string;
  127. first_name?: string;
  128. last_name?: string;
  129. }
  130. interface FormInitStateTypes extends FormProps {
  131. mobile?: {
  132. preValue: string;
  133. realValue: string;
  134. };
  135. }
  136. export type FormType = "login" | "register";
  137. interface Props {
  138. type?: FormType;
  139. }
  140. const FormComponent: FC<Props> = (props) => {
  141. const { type = "register" } = props;
  142. const isStrictMode = useSystemStore().identity_verify.register === 1;
  143. const { setUserInfo } = useUserInfoStore();
  144. const t = useTranslations();
  145. const searchParams = useSearchParams();
  146. const [statusText, setStatusText] = useState("");
  147. const { eventLogin, eventRegister } = useEventPoint();
  148. const formRef = useRef<any>(null);
  149. /// 密码可见
  150. const [visible, setVisible] = useState(false);
  151. const spanClassName = clsx("iconfont", {
  152. "icon-kejian": visible,
  153. "icon-bukejian": !visible,
  154. });
  155. // 是否同意协议
  156. const [checkBoxValue, setCheckBoxValue] = useState<boolean>(false);
  157. const router = useRouter();
  158. /// 初始值
  159. const params = useRef<FormInitStateTypes>({
  160. user_phone: "",
  161. mobile: { preValue: "55", realValue: "" },
  162. pwd: "",
  163. sex: 0,
  164. address: undefined,
  165. birthday: undefined,
  166. email: undefined,
  167. passport: undefined,
  168. referrer_code: undefined,
  169. });
  170. useEffect(() => {
  171. formRef.current?.resetFields();
  172. }, [type]);
  173. const onFinish = (values: FormInitStateTypes) => {
  174. const { mobile } = values;
  175. const newValue = {
  176. ...values,
  177. user_phone: `${mobile?.preValue}${mobile?.realValue}`,
  178. code_phone: mobile?.preValue,
  179. };
  180. delete newValue.mobile;
  181. if (isStrictMode) {
  182. strictHandler(newValue as FormProps);
  183. } else {
  184. looseHandler(newValue as FormProps);
  185. }
  186. };
  187. /// 严格模式
  188. const strictHandler = (values: FormProps) => {
  189. if (type !== "login") {
  190. if (!checkBoxValue) {
  191. Toast.show({
  192. content: t("form.readyAgreement"),
  193. });
  194. return;
  195. }
  196. values.birthday = dayjs(values.birthday).format("YYYY/MM/DD");
  197. }
  198. looseHandler(values);
  199. };
  200. const loginHandler = async (values: FormProps) => {
  201. return new Promise(async (resolve, reject) => {
  202. const loginResult = await loginApi(values).catch((error) => {
  203. let text = error ? t(`code.${error.data.code}`) : t(`code.${500}`);
  204. Toast.show({
  205. content: text,
  206. });
  207. });
  208. if (loginResult?.code === 200) {
  209. eventLogin();
  210. setCookies("Token", loginResult.data.token as string);
  211. // loginAction(loginResult.data.token)
  212. const result = await userInfoApi();
  213. if (result.code === 200) {
  214. setUserInfo(result.data);
  215. resolve(result);
  216. return result;
  217. }
  218. } else {
  219. reject();
  220. }
  221. });
  222. };
  223. /// 宽松模式
  224. const looseHandler = async (values: FormProps) => {
  225. // 请求
  226. Toast.show({
  227. icon: "loading",
  228. duration: 0,
  229. });
  230. // 注册
  231. if (type === "register") {
  232. values.user_name = `${values.first_name} ${values.last_name}`;
  233. delete values.first_name;
  234. delete values.last_name;
  235. const newValues = {
  236. ...values,
  237. referrer_code: sessionStorage.getItem("shareId") ?? undefined,
  238. channel_code: Local.getKey("channel_code") ?? undefined,
  239. // 轮盘邀请
  240. turntable_id: Number(sessionStorage.getItem("turntable_id")) ?? undefined,
  241. turntable_user_id: Number(sessionStorage.getItem("turntable_user_id")) ?? undefined,
  242. turntable_time: Number(sessionStorage.getItem("turntable_time")) ?? undefined,
  243. // 快玩id
  244. click_id: Local.getKey("ban_click_id") ?? undefined,
  245. };
  246. registerApi(newValues)
  247. .then(async (res) => {
  248. if (res.code === 200) {
  249. eventRegister();
  250. loginHandler({
  251. pwd: values.pwd,
  252. user_phone: values.user_phone,
  253. code_phone: values.code_phone,
  254. }).then(() => {
  255. router.replace("/recharge");
  256. Toast.clear();
  257. });
  258. }
  259. })
  260. .catch((error) => {
  261. console.log(`🚀🚀🚀🚀🚀-> in index.tsx on 257`, error);
  262. if (error.data?.code === 1017) {
  263. sessionStorage.removeItem("shareId");
  264. }
  265. Toast.show({
  266. content: t(`code.${error.data?.code ?? 500}`),
  267. });
  268. });
  269. } else {
  270. /// 登录
  271. loginHandler(values).then(() => {
  272. Toast.clear();
  273. const redirect = searchParams.get("redirect")
  274. ? `/${searchParams.get("redirect")}`
  275. : "/";
  276. router.replace(redirect);
  277. });
  278. }
  279. };
  280. const onConfirm = (value: PickerDate) => {
  281. const isChildren = dayjs().subtract(18, "year").isBefore(value);
  282. if (isChildren) {
  283. Toast.show({
  284. icon: "fail",
  285. content: t("form.NotSuitableForChildren"),
  286. });
  287. }
  288. };
  289. const ageValidator = (rule: any, value: PickerDate) => {
  290. const isChildren = dayjs().subtract(18, "year").isBefore(value);
  291. if (isChildren) {
  292. return Promise.reject(new Error(rule.message));
  293. } else {
  294. return Promise.resolve();
  295. }
  296. };
  297. // 手机号验证规则
  298. const checkMobile = (_: any, value: any) => {
  299. const reg = /^.{2}9/;
  300. if (!reg.test(value.realValue)) {
  301. return Promise.reject(new Error(t("form.phoneRules")));
  302. } else if (value.realValue.length < 10) {
  303. return Promise.reject(new Error(t("form.phoneMinReg")));
  304. } else {
  305. return Promise.resolve();
  306. }
  307. };
  308. return (
  309. <Box className={clsx(styles.loginForm, "custom-form")}>
  310. <Form
  311. style={{
  312. "--border-bottom": "none",
  313. "--border-top": "none",
  314. "--border-inner": "none",
  315. }}
  316. ref={formRef}
  317. initialValues={params.current}
  318. onFinish={onFinish}
  319. footer={
  320. <ButtonOwn active className={clsx(styles.btn, "font-bold")}>
  321. {type === "login" ? t("form.loginText") : t("form.registerText")}
  322. </ButtonOwn>
  323. }
  324. >
  325. <Form.Item name="mobile" rules={[{ required: true, validator: checkMobile }]}>
  326. <MobileField />
  327. </Form.Item>
  328. <Form.Item
  329. name="pwd"
  330. label={
  331. <i className="iconfont icon-xiugaimima text-[.18rem] text-[#465461]"></i>
  332. }
  333. extra={
  334. <span className={spanClassName} onClick={() => setVisible(!visible)}></span>
  335. }
  336. layout="horizontal"
  337. rules={[
  338. { required: true, message: t("form.passwordReg") },
  339. { min: 6, max: 20, message: t("form.passwordMinReg") },
  340. ]}
  341. >
  342. <Input
  343. placeholder={t("form.password")}
  344. maxLength={20}
  345. type={visible ? "text" : "password"}
  346. />
  347. </Form.Item>
  348. {type !== "login" && isStrictMode ? (
  349. <>
  350. {/* <Form.Item
  351. name="user_name"
  352. label=""
  353. rules={[{ required: true, message: t("form.usernameReg") }]}
  354. >
  355. <Input placeholder={t("form.username")} />
  356. </Form.Item> */}
  357. <div className="flex items-center">
  358. <Form.Item
  359. name="first_name"
  360. className="mr-[.1rem] w-[1.4rem]"
  361. layout="horizontal"
  362. label={
  363. <i className="iconfont icon-yonghu1 text-[.18rem] leading-[1] text-[#465461]"></i>
  364. }
  365. rules={[{ required: true, message: t("form.usernameReg") }]}
  366. >
  367. <Input placeholder={"Insira seu nome"} />
  368. </Form.Item>
  369. <Form.Item
  370. className="flex-1"
  371. name="last_name"
  372. label=""
  373. rules={[{ required: true, message: t("form.usernameReg") }]}
  374. >
  375. <Input placeholder={"Insira seu sobrenome"} />
  376. </Form.Item>
  377. </div>
  378. <Form.Item
  379. name="birthday"
  380. clickable={false}
  381. trigger={"onConfirm"}
  382. arrowIcon={<i className={"iconfont icon-xiangyou1"}></i>}
  383. onClick={(e, datePickerRef: RefObject<DatePickerRef>) => {
  384. datePickerRef.current?.open();
  385. }}
  386. rules={[
  387. { required: true, message: t("form.birthdayReg") },
  388. {
  389. message: t("form.NotSuitableForChildren"),
  390. validator: ageValidator,
  391. },
  392. ]}
  393. >
  394. <DatePicker
  395. getContainer={null}
  396. min={dayjs().subtract(50, "year").toDate()}
  397. max={dayjs().toDate()}
  398. style={{ background: "#fff", color: "#000" }}
  399. >
  400. {(value) =>
  401. value ? (
  402. dayjs(value).format("YYYY/MM/DD")
  403. ) : (
  404. <span className={"text-[#ccc]"}>{t("form.birthday")}</span>
  405. )
  406. }
  407. </DatePicker>
  408. </Form.Item>
  409. <Form.Item
  410. name="email"
  411. layout="horizontal"
  412. label={
  413. <i className="iconfont icon-youjian text-[.13rem] leading-[1] text-[#465461]"></i>
  414. }
  415. rules={[
  416. { required: true, message: t("form.emailReg"), pattern: emailReg },
  417. ]}
  418. >
  419. <Input placeholder={t("form.email")} />
  420. </Form.Item>
  421. <Form.Item name="sex">
  422. <Radio.Group>
  423. <Radio
  424. value={0}
  425. className={"mr-[0.1rem]"}
  426. style={{ "--icon-size": "18px" }}
  427. >
  428. {t("form.sexMan")}
  429. </Radio>
  430. <Radio value={1} style={{ "--icon-size": "18px" }}>
  431. {t("form.sexWoman")}
  432. </Radio>
  433. </Radio.Group>
  434. </Form.Item>
  435. <Form.Item
  436. name="passport"
  437. layout="horizontal"
  438. label={
  439. <i className="iconfont icon-shenfengzheng text-[.18rem] leading-[1] text-[#465461]"></i>
  440. }
  441. rules={[{ required: true, message: t("form.cardReg"), pattern: neReg }]}
  442. >
  443. <Input placeholder={t("form.card")} maxLength={11} type={"text"} />
  444. </Form.Item>
  445. <Form.Item
  446. name="address"
  447. label=""
  448. rules={[{ required: true, message: t("form.addressReg") }]}
  449. >
  450. <TextArea placeholder={t("form.address")} maxLength={40} />
  451. </Form.Item>
  452. <div className={"flex px-[0.1rem]"}>
  453. <Checkbox
  454. block
  455. style={{ "--icon-size": "16px" }}
  456. checked={checkBoxValue}
  457. onChange={(value) => setCheckBoxValue(value)}
  458. ></Checkbox>
  459. <div className={"ml-[10px] select-none break-all text-[12px]"}>
  460. {t("form.agreement")}
  461. <Link href={"/preventLaunderMoney"} className={"text-[#1677ff]"}>
  462. {t("form.moneyAgreement")}
  463. </Link>
  464. <Link href={"/terms"} className={"text-[#1677ff]"}>
  465. {t("form.serverAgreement")}
  466. </Link>
  467. {t("form.agreementAnd")}
  468. <Link href={"/gamingPolicy"} className={"text-[#1677ff]"}>
  469. {t("form.childrenAgreement")}
  470. </Link>
  471. </div>
  472. </div>
  473. </>
  474. ) : null}
  475. <div className="mb-[0.2rem] flex justify-end text-[0.12rem]">
  476. {type == "login" && (
  477. <>
  478. <Link className={"text-[#677787]"} href="/resetPhone">
  479. {t("LoginPage.forgetPwd")}
  480. </Link>
  481. {/* <Link className={"text-[#fff]"} href="/register">
  482. {t("LoginPage.registerGo")}
  483. </Link> */}
  484. </>
  485. )}
  486. </div>
  487. </Form>
  488. </Box>
  489. );
  490. };
  491. export default FormComponent;